package jfc.core;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import javax.swing.JOptionPane;
import jfc.ui.GamePanel;
import jfc.util.UndoManager;
import jfc.util.Animator;
import jfc.ui.MainWindow;

/**
 * @author Yann Le Gall ylegall@gmail.com Oct 27, 2009 7:28:07 PM
 */
public class GameManager {

    private final Cell[] cells;
    private final Foundation[] foundations;
    private final Cascade[] cascades;
    private CardGroup selectedCards;
    private int movableCards;
    private Animator animator;
    private MainWindow window;
    private UndoManager undoManager;
    private CardHolder previousHolder;
    private final AutoPilot autoPilot;

    public GameManager() {
        selectedCards = new CardGroup();
        undoManager = new UndoManager(this);
        autoPilot = new AutoPilot();

        cells = new Cell[4];
        int x;
        for (int i = 0; i < cells.length; i++) {
            x = 5 + (Card.width() + 5) * (i);
            Point p = new Point(x, 5);
            cells[i] = new Cell(p);
        }

        foundations = new Foundation[4];
        for (int i = 0; i < foundations.length; i++) {
            x = 5 + (Card.width() + 5) * (4 + i);
            Point p = new Point(x, 5);
            foundations[i] = new Foundation(p);
        }

        cascades = new Cascade[8];
        for (int i = 0; i < cascades.length; i++) {
            x = 5 + (Card.width() + 5) * (i);
            Point p = new Point(x, 160);
            cascades[i] = new Cascade(p);
        }
    }

    public final void newGame(MainWindow window) {
        this.window = window;
        for (Cell c : cells) {
            c.clear();
        }
        for (Foundation f : foundations) {
            f.clear();
        }
        for (Cascade c : cascades) {
            c.clear();
        }

        LinkedList<Card> deck = new LinkedList<Card>();
        int val = 0;
        for (; val < 52; val++) {
            deck.add(new Card((val % 13) + 1, Suit.values()[val / 13]));
        }

        Collections.shuffle(deck);

        val = 0;
        while (deck.size() > 0) {
            cascades[val].add(deck.remove());
            val++;
            val = val % 8;
        }

        animator = new Animator(window, selectedCards);
        undoManager.clear();
        window.setRemainingCards(52);
        movableCards = getMovableCards();
    }

    private int getMovableCards() {
        int num = 1;
        for (Cell c : cells) {
            if (c.isEmpty()) {
                num++;
            }
        }

        for (Cascade c : cascades) {
            if (c.numCards() == 0) {
                num = num << 1;
            }
        }
        return num;
    }

    public final int maxColSize() {
        int maxCards = 1;
        for (Cascade c : cascades) {
            int n = c.numCards();
            if (n > maxCards) {
                maxCards = n;
            }
        }
        return maxCards;
    }

    private int getRemainingCards() {
        int total = 52;
        for (Foundation f : foundations) {
            total = total - f.getTopValue();
        }
        return total;
    }

    public void performAction(Point p) {
        if (selectedCards.isEmpty()) {
            selectCards(p);
        } else {
            moveCards(p);
        }
    }

    private void selectCards(Point p) {
        if (p.y < Card.height()) {
            for (Cell c : cells) {
                if (c.contains(p)) {
                    selectedCards.add(c.removeCard());
                    previousHolder = c;
                    return;
                }
            }
        } else {
            for (Cascade c : cascades) {
                if (c.containsPoint(p)) {
                    int index = c.indexOf(p);
                    if (c.isValidStack(index)) {
                        previousHolder = c;
                        selectedCards.addAll(c.getCards(p));
                        if (selectedCards.size() > movableCards) {
                            cancelMove("can't move that many cards");
                            return;
                        }
                        return;
                    }
                }
            }
        }
        selectedCards.clear(); // if nothing contains the point
    }

    private void moveCards(Point p) {
        if (p.y < Card.height()) {
            if (selectedCards.size() == 1) {
                for (Cell c : cells) {
                    if (c.contains(p)) {
                        if(c.isEmpty())
                            doValidMove(c, true);
                        else
                            cancelMove("invalid move");
                        return;
                    }
                }
                for (Foundation f : foundations) {
                    if (f.contains(p)) {
                        if (f.canAdd(selectedCards.get(0))) {
                            doValidMove(f, true);
                            return;
                        }
                    }
                }
            }
            else {
                cancelMove("too many cards.");
                return;
            }
        } else {
            for (Cascade c : cascades) {
                if (c.topCardContains(p)) {
                    if (c.canAdd(selectedCards.get(0))) {
                        if(c.numCards() == 0 && selectedCards.size() > movableCards>>1) {
                            cancelMove("invalid move");
                        }
                        else {
                            doValidMove(c, true);
                        }
                        return;
                    } else {
                        cancelMove("invalid move.");
                        return;
                    }
                }
            }
        }
        cancelMove("");
    }

    private synchronized void doValidMove(CardHolder holder, boolean record) {
        animator.animate(holder.getDestination());
        if (record) {
            undoManager.record(previousHolder, holder, selectedCards.size());
        }
        holder.add(selectedCards);
        int remaining = getRemainingCards();
        window.setRemainingCards(remaining);
        if (remaining == 0) {
            winGame();
            return;
        }
        window.pack();

        movableCards = getMovableCards(); // update the number of moves
        previousHolder = null;
        selectedCards.clear();
        if (record) {
            new Thread(autoPilot).start();
        }
    }

    private void cancelMove(String reason) {
        if (previousHolder != null) {
            previousHolder.add(selectedCards);
            previousHolder = null;
        }
        MainWindow.getInstance().setMessage(reason);
        selectedCards.clear();
    }

    public void undo() {
        undoManager.undo();
    }

    public synchronized void autoMove(CardHolder from, CardHolder to, int num,
            boolean record) {
        cancelMove("");
        selectedCards.addAll(from.remove(num));
        previousHolder = from;
        doValidMove(to, record);
        //window.repaintPanel();
        window.repaint();
    }

    private class AutoPilot implements Runnable {

        public void run() {
            int min = 14;

            // get the minimum value of the foundations:
            for (Foundation f : foundations) {
                int top = f.getTopValue();
                if (top < min) {
                    min = top;
                }
            }

            for (Cell c : cells) {
                if (c.getTopValue() == min + 1) {
                    for (Foundation f : foundations) {
                        if (f.canAdd(c.peekTop())) {
                            autoMove(c, f, 1, true);
                            return;
                        }
                    }
                }
            }
            for (Cascade c : cascades) {
                if (c.getTopValue() == min + 1) {
                    for (Foundation f : foundations) {
                        if (f.canAdd(c.peekTop())) {
                            autoMove(c, f, 1, true);
                            return;
                        }
                    }
                }
            }
        }
    }

    private final void winGame() {
        JOptionPane.showMessageDialog(window, "Congratulations, you win!",
                "You Win!", JOptionPane.INFORMATION_MESSAGE);
    }

    public void draw(Graphics2D g2, boolean drawCards) {
        for (Cell c : cells) {
            c.draw(g2);
        }
        for (Foundation f : foundations) {
            f.draw(g2);
        }
        for (Cascade c : cascades) {
            c.draw(g2);
        }
        if (drawCards) {
            drawCards(g2, true);
        }
    }

    public Rectangle getClip() {
        Rectangle r = new Rectangle(selectedCards.getFirst().getBounds());
        r.x -= 4;
        r.y -= 4;
        r.height += (selectedCards.size() - 1)*GamePanel.CARDGAP + 8;
        r.width += 8;
        return r;
    }

    public void drawCards(Graphics2D g2, boolean highLight) {
        if (selectedCards.size() > 0) {
            if (highLight) {
                g2.setStroke(new BasicStroke(2.0f));
                g2.setColor(Color.CYAN);
            }
            for (Card c : selectedCards) {
                c.draw(g2);
                if (highLight) {
                    g2.drawRoundRect(c.x, c.y, c.width, c.height, 20, 20);
                }
            }
        }
    }

    public void save(FileWriter writer){
        if(writer == null) return;

        try {
            for (Cell c : cells) {
                if (!c.isEmpty()) {
                    writer.write(c.peekTop().toString());
                }
                writer.write(";");
            }

            writer.write("\n");
            for (Foundation f : foundations) {
                writer.write(f.toString());
                writer.write(";");
            }

            writer.write("\n");
            for (Cascade c : cascades) {
                writer.write(c.toString());
                writer.write(";");
                writer.write("\n");
            }
        }
        catch (IOException e) {System.err.println(e.getMessage());}
    }
}
